package com.sl.biz.core.runner.loader;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.pinyin.PinyinUtil;
import com.google.common.collect.Maps;
import com.sl.biz.modular.process.dao.domain.dto.ProcessCompDTO;
import com.sl.biz.modular.process.dao.domain.dto.ProcessCompObjectDTO;
import com.sl.biz.modular.process.dao.domain.dto.ProcessCompVersionDTO;
import com.sl.biz.modular.process.dao.service.ProcessCompDaoService;
import com.sl.biz.modular.process.dao.service.ProcessCompObjectDaoService;
import com.sl.biz.modular.process.dao.service.ProcessCompVersionDaoService;
import com.sl.core.engine.classloader.ProcessClassLoader;
import com.sl.core.engine.plugin.ProcessPluginRegistry;
import com.sl.core.engine.plugin.domain.PluginDecorate;
import com.sl.core.engine.plugin.loader.ProcessPluginLoader;
import com.sl.core.engine.plugin.loader.impl.AbstractProcessPluginLoader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipInputStream;

@Component
@Slf4j
public class DatabaseProcessPluginLoader extends AbstractProcessPluginLoader implements ProcessPluginLoader {

    private Map<String, List<PluginDecorate>> pluginStoreMap = new HashMap<>();


    private final ProcessCompVersionDaoService compVersionDaoService;
    private final ProcessCompObjectDaoService compObjectDaoService;

    private final ProcessCompDaoService compDaoService;

    public static List<String> BASE_PREFIX = Lists.newArrayList();

    static {
        BASE_PREFIX.add("data:application/zip;base64,");
        BASE_PREFIX.add("data:application/x-zip-compressed;base64,");
        BASE_PREFIX.add("data:application/octet-stream;base64,");
        BASE_PREFIX.add("data:multipart/x-zip;base64,");

    }

    public DatabaseProcessPluginLoader(ProcessCompVersionDaoService compVersionDaoService, ProcessCompObjectDaoService compObjectDaoService, ProcessCompDaoService compDaoService) {
        this.compVersionDaoService = compVersionDaoService;
        this.compObjectDaoService = compObjectDaoService;
        this.compDaoService = compDaoService;
        this.init();
    }

    @Override
    public Map<String, List<PluginDecorate>> loader() {
        Map<String, List<PluginDecorate>> pm = getPluginStoreMap(pluginStoreMap);
        for (Map.Entry<String, List<PluginDecorate>> stringListEntry : pm.entrySet()) {
            for (PluginDecorate pluginDecorate : stringListEntry.getValue()) {
                pluginDecorate.setSystemFlag(false);
            }
        }
        return pm;
    }

    public void init() {
        this.init(null, null, null);
    }


    public Map<String, List<PluginDecorate>> init(String orgId, String compCode, String compVersion) {
        //从数据库中加载
        List<ProcessCompVersionDTO> processCompVersionDTOS = compVersionDaoService.queryEnableList(orgId, compCode, compVersion);

        if (CollUtil.isEmpty(processCompVersionDTOS)) {
            return Maps.newHashMap();
        }

        List<String> compCodeList = Lists.newArrayList();
        List<String> compVersionList = Lists.newArrayList();
        List<String> orgList = Lists.newArrayList();


        for (ProcessCompVersionDTO processCompVersionDTO : processCompVersionDTOS) {
            compCodeList.add(processCompVersionDTO.getCompCode());
            compVersionList.add(processCompVersionDTO.getCompVersion());
            orgList.add(processCompVersionDTO.getOrgId());
        }

        List<ProcessCompObjectDTO> processCompObjectDTOS = compObjectDaoService.queryByCodesAndVersions(compCodeList, compVersionList, orgList);

        //获取
        List<ProcessCompDTO> processCompDTOS = compDaoService.queryByCodes(compCodeList, orgList);
        //根据流程编码 流程版本分组
        if (CollUtil.isEmpty(processCompDTOS)) {
            return Maps.newHashMap();
        }
        Map<String, ProcessCompDTO> processCompMap = processCompDTOS.stream().collect(Collectors.toMap(v -> {
            return StrUtil.concat(true, v.getOrgId(), "_", v.getCompCode());
        }, Function.identity(), (k1, k2) -> k1));


        //根据流程编码 流程版本分组
        if (CollUtil.isEmpty(processCompObjectDTOS)) {
            return Maps.newHashMap();
        }
        Map<String, ProcessCompObjectDTO> processCompObjectMap = processCompObjectDTOS.stream().collect(Collectors.toMap(v -> {
            return StrUtil.concat(true, v.getOrgId(), "_", v.getCompCode(), "_", v.getCompVersion());
        }, Function.identity(), (k1, k2) -> k1));
        Map<String, List<PluginDecorate>> pl = Maps.newHashMap();
        for (ProcessCompVersionDTO processCompVersionDTO : processCompVersionDTOS) {
            Map<String, List<PluginDecorate>> slMap = registerComp(processCompVersionDTO, processCompObjectMap, processCompMap);
            mergeMap(slMap,pl);
        }
        return pl;
    }

    private Map<String, List<PluginDecorate>> registerComp(ProcessCompVersionDTO processCompVersionDTO, Map<String, ProcessCompObjectDTO> processCompObjectMap, Map<String, ProcessCompDTO> processCompMap) {
        String compCode = processCompVersionDTO.getCompCode();
        String compVersion = processCompVersionDTO.getCompVersion();
        String orgId = processCompVersionDTO.getOrgId();

        //现在开始注册
        ProcessCompObjectDTO processCompObjectDTO = processCompObjectMap.get(StrUtil.concat(true, orgId, "_", compCode, "_", compVersion));

        ProcessCompDTO processCompDTO = processCompMap.get(StrUtil.concat(true, orgId, "_", compCode));

        if (ObjectUtil.isNull(processCompObjectDTO)) {
            return Maps.newHashMap();
        }
        //现在开始解压并注册
        String pluginId = ProcessPluginRegistry.getId(orgId, compCode, compVersion);
        this.remove(pluginId, true);

        String compFile = processCompObjectDTO.getCompFile();

        File unzip = file(compFile, "", pluginId);

        File file = FileUtil.file(unzip, File.separator + "target" + File.separator + "lib");
        List<File> files = FileUtil.loopFiles(file);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        List<URL> list = new ArrayList<>();

        files = files.stream().sorted(Comparator.comparingInt(v1 -> {
//                String path = v1.getName();
            InputStream is = ZipUtil.get(v1, Charset.defaultCharset(), "/plugin.properties");
            if (ObjectUtil.isNotNull(is)) {
                return 1;
            }
            return 2;
        })).collect(Collectors.toList());

        for (File file1 : files) {
//                String name = file1.getName();
            list.add(URLUtil.getURL(file1));
        }
        if (CollUtil.isEmpty(list)) {
            return Maps.newHashMap();
        }
        ProcessClassLoader processClassLoader = new ProcessClassLoader(list.toArray(new URL[]{}), classLoader);
        Map<String, List<PluginDecorate>> loader = super.loader(processClassLoader);
        Map<String, List<PluginDecorate>> cvLoader = Maps.newHashMap();
        if (MapUtil.isNotEmpty(loader)) {
            for (Map.Entry<String, List<PluginDecorate>> entry : loader.entrySet()) {
                List<PluginDecorate> value = entry.getValue();
                for (PluginDecorate pluginDecorate : value) {
                    pluginDecorate.setClassLoader(processClassLoader);
                    pluginDecorate.setOrgId(processCompVersionDTO.getOrgId());
                    if (StrUtil.isNotBlank(processCompVersionDTO.getCompCode())) {
                        pluginDecorate.setPluginCode(processCompVersionDTO.getCompCode());
                    }
                    if (StrUtil.isNotBlank(processCompDTO.getCompName())) {
                        pluginDecorate.setPluginName(processCompDTO.getCompName());
                    }
                    if (StrUtil.isNotBlank(processCompDTO.getIcon())) {
                        pluginDecorate.setIcon(processCompDTO.getIcon());
                    }
                    if (StrUtil.isNotBlank(processCompVersionDTO.getCompVersion())) {
                        pluginDecorate.setVersion(processCompVersionDTO.getCompVersion());
                    }
                    if (StrUtil.isNotBlank(processCompVersionDTO.getCompDesc())) {
                        pluginDecorate.setDesc(processCompVersionDTO.getCompDesc());
                    }
                    if (StrUtil.isNotBlank(processCompDTO.getGroupName())) {
                        pluginDecorate.setGroupName(processCompDTO.getGroupName());
                    }
                    if (StrUtil.isNotBlank(processCompDTO.getGroupCode())) {
                        pluginDecorate.setGroupCode(processCompDTO.getGroupCode());
                    } else {
                        pluginDecorate.setGroupCode(PinyinUtil.getPinyin(processCompDTO.getGroupName(), ""));
                    }
                }
                cvLoader.put(processCompDTO.getCompCode(), value);
            }
//            pluginStoreMap.merge(cvLoader);
            mergeMap(cvLoader,pluginStoreMap);
        }
        loader.clear();
        return cvLoader;
    }

    protected void mergeMap(Map<String, List<PluginDecorate>> source,Map<String, List<PluginDecorate>> target){
        for (Map.Entry<String, List<PluginDecorate>> sl : source.entrySet()) {

            String key = sl.getKey();
            List<PluginDecorate> value = sl.getValue();
            if(CollUtil.isEmpty(value)){
                continue;
            }
            List<PluginDecorate> pluginDecorates = target.get(key);
            if(ObjectUtil.isNull(pluginDecorates)){
                pluginDecorates =Lists.newArrayList();
                target.put(key,pluginDecorates);
            }
            pluginDecorates.addAll(value);
        }
    }

    public static File file(String f, String prefix, String pluginId) {
        ByteArrayInputStream inputStream = getInputStream(f);

        ZipInputStream zipInputStream = new ZipInputStream(inputStream, Charset.defaultCharset());

        FileUtil.del(prefix + "plugin" + File.separator + pluginId);

        File temp = new File(prefix + "plugin" + File.separator + pluginId);

        //gbk才不会出问题
        File unzip = ZipUtil.unzip(zipInputStream, temp);

        return unzip;
    }

    protected static ByteArrayInputStream getInputStream(String compFile) {


        for (String basePrefix : BASE_PREFIX) {
            compFile = StrUtil.replace(compFile, basePrefix, "");
        }

        byte[] decode = Base64.decode(compFile);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);

        return byteArrayInputStream;
    }

    public void remove(String orgId, String compCode, String compVersion) {
        String pluginId = ProcessPluginRegistry.getId(orgId, compCode, compVersion);
        this.remove(pluginId, true);
    }

    public void remove(String id, Boolean removeFileFlag) {

        ProcessPluginRegistry.INSTANCE.removePlugin(id);

        for (Map.Entry<String, List<PluginDecorate>> entry : pluginStoreMap.entrySet()) {
            List<PluginDecorate> value = entry.getValue();
            value = value.stream().filter(v -> !StrUtil.equals(id, ProcessPluginRegistry.getId(v.getOrgId(), v.getPluginCode(), v.getVersion()))).collect(Collectors.toList());


            List<PluginDecorate> collect = value.stream().filter(v -> StrUtil.equals(id, ProcessPluginRegistry.getId(v.getOrgId(), v.getPluginCode(), v.getVersion()))).collect(Collectors.toList());

            if (CollUtil.isNotEmpty(collect)) {
                for (PluginDecorate pluginDecorate : collect) {
                    ClassLoader classLoader = pluginDecorate.getClassLoader();
                    if (classLoader instanceof ProcessClassLoader cl) {
                        //关闭一把
                        try {
                            cl.close();
//                            cl.closeClassLoader();
                        } catch (Exception e) {

                        }
                    }
                }
            }

            pluginStoreMap.put(entry.getKey(), value);
        }

        for (Map.Entry<String, List<PluginDecorate>> entry : pluginStoreMap.entrySet()) {
            List<PluginDecorate> value = entry.getValue();
            if (CollUtil.isEmpty(value)) {
                pluginStoreMap.remove(entry.getKey());
            }
        }

        if (BooleanUtil.isTrue(removeFileFlag)) {
            try {
                File temp = new File("plugin" + File.separator + id);
                FileUtil.del(temp);
            } catch (Exception e) {
                log.error("移除文件失败", e);
            }
        }
    }

    @Override
    public List<PluginDecorate> get(String code) {
        return this.pluginStoreMap.get(code);
    }

    @Override
    public PluginDecorate get(String orgId, String code, String version) {
        PluginDecorate pluginDecorate = null;
        for (Map.Entry<String, List<PluginDecorate>> entry : this.pluginStoreMap.entrySet()) {
            String k = entry.getKey();
            List<PluginDecorate> v = entry.getValue();

            if (k.equals(code)) {
                if (StrUtil.isBlank(version)) {
                    pluginDecorate = v.stream().findFirst().orElse(null);
                } else {
                    String finalVersion = version;
                    pluginDecorate = v.stream().filter(vv -> vv.getVersion().equals(finalVersion))
                            .filter(vv -> StrUtil.equals(vv.getOrgId(), orgId))
                            .findFirst().orElse(null);
                }
                break;
            }
        }
        return pluginDecorate;
    }

    @Override
    public ByteArrayInputStream getResourceCode(String orgId, String pluginCode, String pluginVersion) {
        ProcessCompObjectDTO processCompObjectDTO = compObjectDaoService.queryByCodeAndVersion(orgId, pluginCode, pluginVersion);

        if (ObjectUtil.isNull(processCompObjectDTO)) {
            return null;
        }
        return getInputStream(processCompObjectDTO.getCompFile());
    }
}
